package me.flyingrub.mibandnotify.bluetooth;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import java.util.Arrays;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import me.flyingrub.mibandnotify.miband.MiBandConnectFailureException;
import me.flyingrub.mibandnotify.miband.MiBandConstants;
/**
* Created by Lewis on 01/01/15.
*/
public class BLECommunicationManager {
private String TAG = this.getClass().getSimpleName();
/*
Read as: cc 01 f4 01 00 00 f4 01 f2 01 60 09
Written as: cc 01 f4 01 00 00 f4 01 00 00 00 00
connIntMin: 575ms
connIntMax: 625ms
latency: 0ms
timeout: 5000ms
connInt: 622ms
advInt: 1500ms
Read as: 27 00 31 00 00 00 f4 01 30 00 60 09
Written as: 27 00 31 00 00 00 f4 01 00 00 00 00
connIntMin: 48ms
connIntMax: 61ms
latency: 0ms
timeout: 5000ms
connInt: 60ms
advInt: 1500ms
Read as: 06 00 50 00 02 00 d0 07 63 00 60 09
Written as:
connIntMin: 7ms
connIntMax: 100ms
latency: 2ms
timeout: 20000ms
connInt: 123ms
advInt: 1500ms
*/
public byte[] mCurrentLeParams;
public static final byte[] mLowLatencyLeParams = new byte[]{0x27, 0x00, 0x31, 0x00, 0x00, 0x00, (byte)0xf4, 0x01, 0x00, 0x00, 0x00, 0x00};
public static final byte[] mHighLatencyLeParams = new byte[]{(byte)0xcc, 0x01, (byte)0xf4, 0x01, 0x00, 0x00, (byte)0xf4, 0x01, 0x00, 0x00, 0x00, 0x00};
private int attempts = 0;
private String mDeviceAddress;
private BluetoothGatt mGatt;
private boolean mDeviceConnected = false;
private final Context mContext;
public boolean mBluetoothAdapterStatus = false;
public boolean setupComplete = false;
private BluetoothDevice mBluetoothMi;
private BluetoothGattCharacteristic mControlPointChar;
private BlockingQueue<BLEAction> bleActions = new LinkedBlockingDeque<>();
private Thread senderThread;
public BLECommunicationManager(final Context context) {
this.mContext = context;
setupBluetooth();
senderThread = new Thread() {
public void run() {
while (true) {
BLEAction action = null;
try {
action = bleActions.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
if(action instanceof WaitAction) {
action.run();
}
else if(action instanceof WriteAction) {
try {
BluetoothGattCharacteristic characteristic = getCharacteristic(((WriteAction) action).getCharacteristic());
characteristic.setValue(((WriteAction) action).getPayload());
write(characteristic);
} catch(MiBandConnectFailureException e) {
Log.i(TAG, "Write failed");
}
}
}
}
};
senderThread.start();
}
public void queueTask(BLEAction bleAction) {
bleActions.offer(bleAction);
}
public void setupBluetooth() {
Log.d(TAG, "Initialising Bluetooth connection");
if(BluetoothAdapter.getDefaultAdapter().isEnabled()) {
attempts += 1;
final BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
final Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
for(BluetoothDevice pairedDevice : pairedDevices) {
if(pairedDevice.getName().equals("MI") && pairedDevice.getAddress().startsWith(MiBandConstants.MAC_ADDRESS_FILTER)) {
mDeviceAddress = pairedDevice.getAddress();
}
}
if(mDeviceAddress == null) {
Log.d(TAG, "Can't find Bluetooth by paired devices. Trying by MAC address");
//mDeviceAddress = UserPreferences.getInstance().getMiBandMAC();
if(mDeviceAddress.equals("")) {
Log.d(TAG, "Manual MAC address not found. Please set it.");
return;
}
}
if(mDeviceAddress != null) {
mBluetoothMi = mBluetoothAdapter.getRemoteDevice(mDeviceAddress);
attempts = 0;
setupComplete = true;
mBluetoothAdapterStatus = true;
try {
connectGatt();
}
catch(MiBandConnectFailureException e) {
Log.w(TAG, "Could not connect to Mi Band");
}
Log.d(TAG, "Initialising Bluetooth connection complete");
}
else {
//Wait 10 seconds and try again, sometimes the Bluetooth adapter takes a while.
if(attempts <= 10) {
try {
Thread.sleep(10000);
}
catch(InterruptedException e) {
e.printStackTrace();
}
setupBluetooth();
}
}
}
}
private void connectGatt()
throws MiBandConnectFailureException {
Log.d(TAG, "Establishing connection to gatt");
mGatt = mBluetoothMi.connectGatt(mContext, true, mGattCallback);
//TODO: Register for connection state change.
mGatt.connect();
Log.d(TAG, "GATT : " + mGatt);
}
public void disconnectGatt() {
if(mGatt != null) {
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
mGatt.disconnect();
mGatt.close();
mGatt = null;
}
});
}
}
public void write(final BluetoothGattCharacteristic characteristic) throws MiBandConnectFailureException {
if(BluetoothAdapter.getDefaultAdapter().isEnabled()) {
if(mDeviceConnected) {
Log.d(TAG, "Writing!");
mGatt.writeCharacteristic(characteristic);
}
else {
Log.d(TAG, "Device not connected, connecting");
connectGatt();
write(characteristic);
}
} else {
Log.d(TAG, "Bluetooth not enabled, write failed");
}
}
private void setLowLatency() throws MiBandConnectFailureException {
final BluetoothGattCharacteristic characteristic = getCharacteristic(MiBandConstants.UUID_CHARACTERISTIC_LE_PARAMS);
characteristic.setValue(mLowLatencyLeParams);
write(characteristic);
//TODO: This currently doesn't work, as the device doesn't react fast enough to change the connection before the next write.
}
public void setHighLatency() {
try {
final BluetoothGattCharacteristic characteristic = getCharacteristic(MiBandConstants.UUID_CHARACTERISTIC_LE_PARAMS);
characteristic.setValue(mHighLatencyLeParams);
write(characteristic);
}
catch(MiBandConnectFailureException ignored) {
}
}
private BluetoothGattService getMiliService() {
return mGatt.getService(MiBandConstants.UUID_SERVICE_MILI_SERVICE);
}
public BluetoothGattCharacteristic getCharacteristic(UUID uuid) {
if(MiBandConstants.UUID_CHARACTERISTIC_CONTROL_POINT.equals(uuid) && mControlPointChar != null) {
return mControlPointChar;
}
else if(MiBandConstants.UUID_CHARACTERISTIC_CONTROL_POINT.equals(uuid) && mControlPointChar == null) {
mControlPointChar = getMiliService().getCharacteristic(uuid);
return mControlPointChar;
}
return getMiliService().getCharacteristic(uuid);
}
private BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if(status == BluetoothGatt.GATT_SUCCESS) {
Log.d(TAG, "CONNECTED");
}
}
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
mGatt = gatt;
switch(newState) {
case BluetoothProfile.STATE_CONNECTED:
Log.d(TAG, "Gatt state: connected");
gatt.discoverServices();
mDeviceConnected = true;
break;
default:
Log.d(TAG, "Gatt state: not connected");
mDeviceConnected = false;
break;
}
}
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
Log.d(TAG, "Write successful: " + Arrays.toString(characteristic.getValue()));
}
};
}